#! /bin/bash
# ex: ts=8 sw=4 sts=4 et filetype=sh
if [ -n "$DEBUG" ]; then
    set -x
    export PS4='+$BASH_SOURCE:$LINENO: '
fi

set -e
set -h
set -u

umask 022

VIAHOME=$HOME/via

ROOT=/

TEST=""

PREFIX="/usr"

CARCH="x86_64"
CHOST="x86_64-unknown-linux-gnu"

# -march : cpu to build for
# -mtune : generic optimization for -march
# -O2    : optimization level for code
# -pipe  : use pipes instead of tmp files for compiler communication
CFLAGS="-march=native -mtune=native -O2 -pipe"
CXXFLAGS="-march=native -mtune=native -O2 -pipe"

# --hash-style : use gnu hash tables
# --as-needed  : only add libraries to DT_NEEDED that resolve 
LDFLAGS="-Wl,--hash-style=gnu -Wl,--as-needed"
MAKEFLAGS="-j3"

GPG_USER="test@test.com"

chroot=$VIAHOME/chroot
repo=$VIAHOME/repo
cache=$VIAHOME/cache
plans=$VIAHOME/plans
logs=$VIAHOME/logs
log=$VIAHOME/logs/via.log

builds=$cache/builds
sources=$cache/sources
stages=$cache/stages
packages=$cache/packages


init() {
    # setup cache dir end log dir
    for d in $logs $repo $builds $sources $stages $packages; do
        if [ ! -d $d ]; then
            #log $FUNCNAME $d
            mkdir -p $d
        fi
    done

    export CFLAGS CXXFLAGS LDFLAGS MAKEFLAGS CHOST
}

# function download()
# downloads a url
# $1 = url
# example:
# download http://www.foo.org/foo.tar.gz
download() {
    if [ "$source" == "NIL" ]; then
        echo "RETURN"
        return
    fi

    local url="$1"
    local file="$(basename $url)"

    if [ "$url" = "nil" ]; then
        log "$FUNCNAME" "no source file"
        return 0
    fi
    _pushd "$sources"
    if [ ! -f "$file" ]; then
        log "$FUNCNAME" "$url"
        curl -f -L -# -O "$url"
        echo
    fi	
    _popd

}

# FIXME: this whole function is a mess rework it properly
via_stage() {
    source_plan $1
    if is_func_defined "custom_stage"; then
        custom_stage
        return
    fi
    local file="$(basename $source)"
    local btype="$(echo $builder)"

    log "$FUNCNAME" "$name"

    if [ "$file" = "nil" ]; then
        log "$FUNCNAME" "no source file"
        [ ! -d "$builds/$build_name" ] && mkdir "$builds/$build_name"
        [ ! -d "$stages/$build_name" ] && mkdir $stages/$build_name
        return 0
    fi
    _pushd "$stages"
    if [ ! -d "$build_name" ]; then
        case $file in
            *zip) bsdtar xf $sources/$file ;;
            *) tar xf "$sources/$file";;
        esac
        if [ -d "$plan_dir/patches" ]; then
            _pushd "$build_name"
            for i in $plan_dir/patches/*; do
                log "applying" "$(basename $i)"
                patch -Np1 -i "$i"
            done
            _popd
        fi
    fi
    _popd
    _pushd $stages/$build_name
    btype=$(get_build_type)
    _popd $stages/$build_name
    if [ ! -d "$builds/$build_name" ] && [ "$btype" = "gnu_build" ]; then
        mkdir "$builds/$build_name"
    fi

    if [ ! "$btype" = "gnu_build" ] && [ ! -d "$builds/$build_name" ]; then
        cp -af "$stages/$build_name" "$builds"
    fi
}

via_build_core() {
    if [ "$ROOT" = "/" ]; then
        log "ERROR" "ROOT is set to / this is wrong!"
        log "ERROR" "specify target dir with -r"
        exit 1
    fi
    local db=$ROOT/var/db/via
    for i in $plans/core/*; do
        source_plan "$(basename $i)"
        if [ ! -d "$db/$name-$version" ] ; then
            via build "$name"
            via -r $ROOT install "$name"
        else
            log skipping $name
        fi
    done
}

via_build_install() {
    via_build $1
    sudo -E via install "$1"
}

source_plan() {
    source $plans/*/$1/plan

    source="${source:-NIL}"
    builder="${builder:-NIL}"
    packager="${packager:-NIL}"

    plan_dir=$(echo $plans/*/$1)
    files_dir="$plan_dir/files"

    if [ "${build_name:-NIL}" = "NIL" ]; then
        build_name=$name-$version
    fi

    if [ "${build_args:-NIL}" = "NIL" ]; then
        build_args=""
    fi

    export DESTDIR="$packages/$name-$version"

    if [ "${package_args:-NIL}" = "NIL" ]; then
        package_args=""
    fi
}

get_build_type() {
    # if builder is set use it.
    if [ "$builder" != "NIL" ]; then
        printf "$builder"
        return 0
    fi

    if is_func_defined "custom_build"; then
        printf "custom_build"
        return 0
    fi

    # check for configure if it exists set gnu_build
    if [ -f "configure" ]; then
        printf "gnu_build"
        return 0
    fi

    if [ -f "Makefile" ]; then
        printf "gnu_make"
        return 0
    fi

    if [ -f "setup.py" ]; then
        printf "python_build"
        return 0
    fi

    echo "ERROR" "could not determine builder type"
    return 1
}

get_package_type() {
    # if packager is set use it.
    if [ "$packager" != "NIL" ]; then
        printf "$packager"
        return 0
    fi

    if is_func_defined "custom_install"; then
        printf "custom_install"
        return 0
    fi

    # check for configure if it exists set gnu_package
    if [ -f "configure" ]; then
        printf "gnu_package"
        return 0
    fi

    if [ -f "Makefile" ]; then
        printf "gnu_package"
        return 0
    fi

    if [ -f "setup.py" ]; then
        printf "python_package"
        return 0
    fi

    log "ERROR" "could not determine packager type"
    return 1
}

is_cross_build() {
    if [ -n "${CLFS_TARGET:-}" ]; then
        true
    else
        false
    fi
}

is_func_defined() {
    if [ "$(type -t $1)" = "function" ]; then
        true
    else
        false
    fi
}

is_installed() {
    name_version="$($VIAHOME/via get_version $1)"
    if [[ -d $ROOT/var/db/via/$name_version ]]; then
        true
    else
        false
    fi
}

via_build() {
    source_plan $1
    log starting $name
    download $source
    case $verify in
        gnu_verify)
            gnu_verify $source
            ;;
        sign_verify)
            sign_verify $source
            ;;
        no_verify)
            ;;
        *)
            sha_verify $source
            ;;
    esac

    via_stage $name

    _pushd "$stages/$build_name"
    builder=$(get_build_type)
    build_type=$(echo $builder)
    _popd

    _pushd $builds/$build_name
    log "$build_type" "$name"

    if is_func_defined 'cross_build' && is_cross_build; then
        cross_build
    fi

    $builder
    if [ "$build_type" = "gnu_build" ] && [ -n "$TEST" ]; then
        make check
    fi
    via_package $name
    _popd

    log done "$name-$version"
}

strip_package() {
    STRIP_SHARED="--strip-unneeded"
    STRIP_BINARIES="--strip-all"
    STRIP_STATIC="--strip-debug"
    if is_cross_build; then
        STRIP_CMD="${CLFS_TARGET}-strip"
    else
        STRIP_CMD="strip"
    fi
    find . -type f -perm -u+w 2>/dev/null | while read binary ; do
        case "$(file -bi "$binary")" in
            *application/x-sharedlib*)  # Libraries (.so)
                log "strip shared" "$binary"
                $STRIP_CMD $STRIP_SHARED "$binary";;
            *application/x-archive*)    # Libraries (.a)
                log "strip static" "$binary"
                $STRIP_CMD $STRIP_STATIC "$binary";;
            *application/x-executable*) # Binaries
                log "strip binary" "$binary"
                $STRIP_CMD $STRIP_BINARIES "$binary" || true ;;
        esac
    done
}

is_binary_or_lib() {
    case "$(file -bi "$1")" in
        *application/x-sharedlib*) 
            true;;
        *application/x-executable*) # Binaries
            true;;
        *)
            false;;
    esac
}

write_depends() {
    local db=$ROOT/var/db/via
    touch DEPENDS
    if [[ ! -d $db ]]; then
        log "WARNING" "$db does not exist"
        return
    fi
    find . -type f 2>/dev/null | while read binary ; do
        if ! is_binary_or_lib "$binary"; then
            continue
        fi
        libs=$(objdump -x "$binary" | grep NEEDED | awk '{print $2}')
        for i in $libs; do
            if grep -q $i MANIFEST; then
                continue
            fi
            manifest=$(grep -lR "$i" "$db") || true
            if [[ -z $manifest ]]; then
                log "WARNING" "$i not resolved"
                continue
            fi
            dir=$(dirname "$manifest")
            depend=$(basename $dir | awk -F '-' '{print $1}')
            if ! grep -q "$depend" DEPENDS; then
                log "$i" "$depend"
                echo $depend >> DEPENDS
            fi
        done
    done
}

via_package() {
    source_plan "$1"
    local arch="${TARGET_ARCH:-$(arch)}"
    local pkgfile="$repo/$arch/$name-$version-via.tar.gz"
    [ ! -d "$(dirname $pkgfile)" ] && mkdir $(dirname $pkgfile)
    log $FUNCNAME "$name"
    if [ -d $DESTDIR ]; then
        log "cleaning" "$(basename "$DESTDIR")"
        rm -rf $DESTDIR
    fi
    log $(echo $packager) $name
    packager=$(get_package_type)
    _pushd $builds/$build_name
        $packager
    _popd
    package_files_dir
    if [ ! -d $DESTDIR ]; then
        mkdir $DESTDIR
    fi
    if [ -n "${CLFS_TARGET:-}" ] || [ "$PREFIX" = "/tools" ]; then
        _pushd $DESTDIR/tools
    else
        _pushd $DESTDIR
    fi
    if [ "$(type -t post_hook)" = "function" ]; then
        post_hook
    fi
    strip_package
    find . > MANIFEST
    write_depends
    log "compressing" "$(basename $pkgfile)"
    fakeroot tar cfz $pkgfile *
    if ! is_cross_build; then
        cp DEPENDS MANIFEST "$plan_dir/"
    fi
    _popd
    _pushd $repo
    log "signing" "$(basename $pkgfile)"
    [ -f $pkgfile.sig ] && rm $pkgfile.sig
    gpg -u $GPG_USER --detach-sign $pkgfile
    log $pkgfile $(green "OK")
    if ! gpg --verify $pkgfile.sig &> /dev/null; then
        gpg --verify $pkgfile.sig 
    fi
    _popd
}

package_files_dir() {
    if [ ! -d $files_dir ]; then
        return
    fi
    for f in $files_dir/*; do
        local file=$(head -1 < $f | awk '{print $2}')
        local mode=$(head -1 < $f | awk '{print $3}')
        log "installing" "$file"
        install -Dm $mode $f $DESTDIR/$file
    done
}

install_core() {
    via -r $ROOT install filesystem
    for i in $plans/core/*; do
        via -r $ROOT install "$(basename $i)"
    done
    cp /etc/ld.so.conf $ROOT/etc
    cp /etc/nsswitch.conf $ROOT/etc
    ldconfig -r $ROOT
    chroot $ROOT groupadd -g 81 dbus
    chroot $ROOT useradd -u 81 -g dbus -d / -s /bin/false dbus
    chroot $ROOT passwd -l dbus
    chroot $ROOT dbus-uuidgen --ensure
    chroot $ROOT groupadd -g 54 lock
    chroot $ROOT systemd-machine-id-setup
    log "core" "installed"
}

diff_manifest() {
    local fullname=$name-$version
    local pkgfile="$repo/${TARGET_ARCH:-$(arch)}/$fullname-via.tar.gz"
    local db=var/db/via
    while read line; do
        case "${line::1}" in
            "<")
                echo $(red "$line");;
            ">")
                echo $(green "$line");;
            *)
                echo "$line";;
        esac
    done < <(diff $db/$fullname/MANIFEST <(tar xOf $pkgfile MANIFEST))
}

via_cross_install() {
    TARGET_ARCH=arm
    ROOT=/tools
    via_install $1
}


install_depends() {
    while read depend; do
        if ! is_installed "$depend"; then
            via_install "$depend" "no"
        fi
    done < <(tar xOf $pkgfile DEPENDS)
}

via_install() {
    source_plan $1
    local with_depends=${2:-}
    local fullname=$name-$version
    local pkgfile="$repo/${TARGET_ARCH:-$(arch)}/$fullname-via.tar.gz"
    local db=var/db/via

    [ -d $ROOT ] || mkdir -p $ROOT

    if ! gpg --verify $pkgfile.sig &> /dev/null;then
        gpg --verify $pkgfile.sig
        exit 1
    fi

    if [[ -z $with_depends ]]; then 
        install_depends $pkgfile
    fi

    _pushd $ROOT
    [ -d $db ] || mkdir -p $db
    if [ -f MANIFEST ]; then
        log $FUNCNAME "$(red "ERROR") $fullname: MANIFEST exists"
        return 1
    fi
    if is_installed $1; then
        diff_manifest
        log $(yellow WARNING) "$fullname is already installed"
        echo -n "reinstall ? y/n "
        read ok
        if [ "$ok" = "N" ] || [ -z "$ok" ] || [ "$ok" != "y" ]; then
            echo "not installing"
            return 0
        fi
        via_remove $1
    fi
    logf "installing" $(basename $pkgfile)
    mkdir -p "$db/$fullname"
    tar -xf $pkgfile | true
    mv MANIFEST DEPENDS $db/$fullname/
    _popd
    ldconfig -r $ROOT &> /dev/null || true
    echo " " $(green "done")
}

via_remove() {
    local db=var/db/via
    _pushd $ROOT
    local package=$(basename $db/$1-[0-9]*/)
    log "removing" $package
    local manifest=$db/$package/MANIFEST
    # remove files
    while read file; do
        if [ -f $file ]; then
            rm $file
        fi
    done < $manifest
    rm -r $db/$package
    _popd
    log $package "removed"
}

# <action> <details>
logf() {
    local blue=$(tput setaf 4)
    local reset=$(tput sgr0)
    printf "${blue}via: ${reset}%-20.20s %-40.40s" "$1" "$2"
}

# <action> <details>
log() {
    local blue=$(tput setaf 4)
    local reset=$(tput sgr0)
    printf "${blue}via: ${reset}%-20.20s %-40.40s\n" "$1" "$2"
}

_pushd() {
    builtin pushd "$@" > /dev/null
}

_popd() {
    builtin popd &> /dev/null
}

# BUILD HOOKS
CONFIG="--prefix=$PREFIX --sysconfdir=/etc --enable-shared \
    --localstatedir=/var --with-shared --config-cache --disable-static"

relocate_build() {
    gnu_build
}

STDBUF=1048576
gnu_build() {
    $stages/$build_name/configure $CONFIG --libexec=$PREFIX/lib/$name $build_args
    make
}

no_build() {
    log $FUNCNAME "$name"
}

gnu_package() {
    if [[ -z ${package_args:-} ]]; then
        fakeroot make DESTDIR=$DESTDIR install
    else
        fakeroot make DESTDIR=$DESTDIR $package_args
    fi
}

no_install() {
    log $FUNCNAME "$name"
}

gnu_make() {
    make $build_args
}

no_verify() {
    log $FUNCNAME "no file to verify"
}

python_build() {
    python setup.py build
}

python_package() {
    python setup.py install --root="$DESTDIR/" --optimize=2
}

# VERIFY HOOKS
gnu_verify() {
    local url=$1
    local ext=${2:-sig}
    local file=$(basename $url)
    log $FUNCNAME $name
    _pushd $sources
    if [ ! -f $file.$ext ]; then
        download $url.$ext
    fi
    if ! gpg --verify $file.$ext &> /dev/null; then
        log $FUNCNAME $(red FAILED)
        gpg --verify $file.$ext
        return 1
    fi	
    log $FUNCNAME $(green PASS)
    _popd
}

sign_verify() {
    gnu_verify $1 sign
}

sha_verify() {
    local url=$1
    local file=$(basename $url)
    log $FUNCNAME $name
    _pushd $sources
    sha=$(sha256sum $file | awk '{print $1}')
    if [ ! "$sha" = "$verify" ]; then
        log $FUNCNAME $(red FAILED)
        echo $sha
        return 1
    fi	
    log $FUNCNAME $(green PASS)
    _popd
}
# END HOOKS


red() {
    local red=$(tput setaf 1)
    local reset= #$(tput sgr0)
    echo ${red}$1${reset}
}

green() {
    local green=$(tput setaf 2)
    local reset=$(tput sgr0)
    echo ${green}$1${reset}
}

yellow() {
    local yellow=$(tput setaf 3)
    local reset=$(tput sgr0)
    echo "${yellow}${1}${reset}"
}

blue() {
    local blue=$(tput setaf 4)
    local reset=$(tput sgr0)
    echo ${blue}$1${reset}
}

via_edit() {
    local name=$1
    $EDITOR $plans/*/$name/plan
}

via_mkvirt() {
    local image=./vanilla.img
    local target=./vanilla
    trap "umount $target" ERR
    [ ! -d $target ] && mkdir $target
    ROOT=$target
    qemu-img create -f raw $image 700M
    mkfs.ext4 -F $image
    mount -o loop $image $target
    install_core
    echo "/dev/sda / ext4 defaults 1 2" > $ROOT/etc/fstab
    echo "vanvirt" > $ROOT/etc/hostname
    mount -o bind /dev $target/dev
    chroot $target passwd
    umount $target/dev
    umount $target
    log $FUNCNAME "completed"
}

via_virt() {
    local kernel=/boot/vmlinuz-*
    local initrd=/boot/initramfs-*.img
    echo "starting emulator"
    qemu-system-x86_64 -hda ./vanilla.img \
        -initrd $initrd \
        -kernel $kernel \
        -nographic -append "console=ttyS0 root=/dev/sda init=/bin/systemd"
    #-append "root=/dev/sda"
}

via_import() {
    local abs=/var/abs/
    local target=$1
    local path=$(find $abs -type d -name $target)
    if [ -d $plans/*/$target ]; then
        log $FUNCNAME "$target already exists"
        return 1
    fi
    if [ -z "$path" ]; then
        log $FUNCNAME "$1 abs not found"
        return
    fi
    echo $path
    source $path/PKGBUILD
    source=${source/$pkgver/\$version}
    local t=$plans/incoming/$target/plan
    mkdir -p $plans/incoming/$target
    cp -v $plans/plan.proto $t
    sed -i "s,NAME,$pkgname," $t
    sed -i "s,VERSION,$pkgver," $t
    sed -i "s,SOURCE,$source," $t
    via_gen_hash $target
    cat $path/PKGBUILD
    log $FUNCNAME "$target imported"
    #via build_install $target
}

via_list() {
    local db=$ROOT/var/db/via
    cat $db/$1-*/MANIFEST
}

via_gen_hash() {
    source_plan $1
    download $source
    local plan=$plans/*/$1/plan
    local file=$(basename $source)
    sha=$(sha256sum $sources/$file | awk '{print $1}')
    echo verify=\"$sha\" >> $plan
}

via_remove_plan() {
    rm -rv $(find $plans -name $1)
}

via_cross_build() {
    TARGET_ARCH="arm"
    CROSS_TOOLS=/cross-tools
    ABI="gnueabi"
    unset CHOST CARCH CFLAGS CXXFLAGS LDFLAGS
    CLFS_HOST="$(arch)-cross-linux-gnu"
    if [[ $TARGET_ARCH == "arm" ]]; then
        CLFS_TARGET="${TARGET_ARCH}-linux-${ABI}"
    else
        CLFS_TARGET="${TARGET_ARCH}-linux-gnu"
    fi
    export PATH=/usr/lib/ccache/bin:/cross-tools/bin:/bin:/usr/bin

    export HOST_CC=gcc
    export CC="${CLFS_TARGET}-gcc"
    export CXX="${CLFS_TARGET}-g++"
    export AR="${CLFS_TARGET}-ar"
    export AS="${CLFS_TARGET}-as"
    export RANLIB="${CLFS_TARGET}-ranlib"
    export LD="${CLFS_TARGET}-ld"
    export STRIP="${CLFS_TARGET}-strip"

    PREFIX=/tools
    ROOT=/tools
    CONFIG+="\
        --prefix=$PREFIX \
        --sysconfdir=$PREFIX/etc \
        --localstatedir=$PREFIX/var \
        --with-shared \
        --build=${CLFS_HOST} \
        --host=${CLFS_TARGET} \
        --target=${CLFS_TARGET}"

    via_build $1
    via_cross_install $1
}

via_repo_sync() {
    rsync -augzv $VIAHOME/repo/arm -e ssh android:/tools/via/repo/
}

via_get_version() {
    source_plan $1
    echo $name-$version
}

via_build_repo() {
    for i in $plans/$1/*; do
        plan="$(basename $i)"
        if ! is_installed $plan; then
            via build_install $plan
        fi
    done
}

via_setup_tools() {
    CLFS=/mnt/clfs
    via -r $CLFS install filesystem || true
    mount -v --bind /dev $CLFS/dev 
    mount -vt devpts devpts $CLFS/dev/pts
    mount -vt tmpfs shm $CLFS/dev/shm
    mount -vt proc proc $CLFS/proc
    mount -vt sysfs sysfs $CLFS/sys
    mount -v -o bind $HOME $CLFS/root
}

via_enter_chroot() {
    CLFS=/mnt/clfs
    chroot "$CLFS" /tools/bin/env -i \
        HOME=/root TERM="$TERM" PS1='\u:\w\$ ' \
        PATH=/bin:/usr/bin:/sbin:/usr/sbin:/tools/bin \
        /tools/bin/bash --login +h
}

via_bootstrap_core() {
    while read line; do
        if [[ ${line::1} == "#" ]] || [[ ${line::1} == "" ]]; then
            continue
        fi
        name_version="$(./via get_version $line)"
        if [[ -d /var/db/via/$name_version ]]; then
            continue
        fi
        ./via build_install $line
    done < "cross/core.list"
}

via_install_android() {
    TARGET_ARCH=arm
    ROOT=$PWD/android
    log "android install" "$(green "started")"
    for i in bash curl coreutils git tar gnupg nmap; do
        if is_installed $i; then
            continue
        fi
        via_install $i
    done
    log "android install" "$(green "finished")"
}

while getopts ":r:" opt; do
    case $opt in
        r)
            ROOT=$OPTARG
            shift 2
            ;;
        \?)
            ;;
        :)
            ;;
    esac
done

init
action=$1
shift
via_$action $@
